/***************************************************************************
 * This code was adapted from TSPC_CCID.cs which was originally written by *
 * Teridian SemiConductor, Corp. It has been modified from its original    *
 * version to allow for use in the .NET Micro Framework. It has also been  *
 * adapted to allow for use with the GemCore Serial Lite Pro evaluation    *
 * board. Additions have been made to the original version to allow for a  *
 * broader use of the reader.                                              *
 * LAST MODIFIED: 2010 April 23                                            *
 * Copyright (C) 2009 Gemalto                                              *
 ***************************************************************************/
/***************************************************************************
 *  Device.cs is part of the PC/SC Micro API for the .NET Micro            *
 *  Framework.                                                             *
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is free software:     *
 *  you can redistribute it and/or modify it under the terms of the GNU    *
 *  General Public License as published by the Free Software Foundation,   *
 *  either version 2 of the License, or (at your option) any later version.*
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is distributed in     *
 *  the hope that it will be useful, but WITHOUT ANY WARRANTY; without even*
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    *
 *  PURPOSE.  See the GNU General Public License for more details.         *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with the PC/SC Micro API for the .NET Micro Framework. If not,   *
 *  see <http://www.gnu.org/licenses/>.                                    *
 ***************************************************************************/
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace PCSCMicro
{
    internal class Device : Reader
    {
        #region Definition of constants and global variables

        // Collecting Data TimeOut Design Setting
        int TestTimeOut = 15 * 60;       // Timeout for APDU calculated to second
        int InitializationTimeOut = 10;  // Apply to non-APDU and non-EMV testing command, in second
        int TimeOutUnitInMiliSec = 500;  // This TimeOut is independent with the ones in Min and Sec
        
        // Hard-coded configuration constants
        const string ACK = "60";
        const string NACK = "E0";
        const string PowerOnSC = "0112FF";
        const string PowerOffSC = "0111FF";
        const string CheckStatus = "0117FF";
        const string SetParametersCommand = "0612";
        const string RestartReader = "040C000000FF";
        
        // Dynamic Strings used for commands
        string NAD = "42";
        string PCB = "00";
        string Config_Init;

        // Bit to keep track of sent parity
        bool pcb = false;
        const string onPCB = "40";
        const string offPCB = "00";

        // Keep track of slot status
        const int ACTIVATED = 0;
        const int PRESENT = 1;
        const int NOCARD = 2;
        const int ERROR = 3;
        int SlotStatus = NOCARD;

        // Maximum number of log lines
        const int MaxBytes = 4096;

        // Flags used for running processes
        bool IsSerialPortOn = false;
        bool APDUCommandComplete = true;
        bool ActivatingSmartCard = false;
        bool CardActivated = false;
        bool ResettingCard = false;
        
        // Retry counters for Serial Port Communication
        int NumberOfRetries;
        int NumberOfGemCoreRetries;

        // Serial Port
        int baudrate = 9600;
        System.IO.Ports.SerialPort ComPort = new System.IO.Ports.SerialPort(System.IO.Ports.Serial.COM1);

        // Packet parser
        PacketParser Packet = new PacketParser();

        // PPS Strings
        string Protocol = "00";//Protocol to be set by user later
        string FiDi = "11";//FiDi to be set by user later
        string PRT = "50"; // T = 0, EDC = LRC, I-Block Sent = 1, I-Block Received = 0
        string TC1 = "00";
        string TC2_TA3 = "10";
        string RFU_TB3 = "00";

        // Card characteristics
        ReaderError error = ReaderError.NONE;
        string atr = null;

        #endregion

        /*****************************************************************************/
        /*                                                                           */
        /*      Serial Port Communication: Initialize, Send and Receive Data         */
        /*                                                                           */
        /*****************************************************************************/

        #region Serial Port Interface

        // Initialize Serial Port 
        void InitializeComPort(int baudrate, System.IO.Ports.Handshake handShake)
        {
            if (!ComPort.IsOpen)
            {
                // Set COM port properties
                ComPort.BaudRate = baudrate;
                ComPort.Parity = System.IO.Ports.Parity.None;
                ComPort.StopBits = System.IO.Ports.StopBits.One;
                ComPort.DataBits = 8;
                ComPort.Handshake = handShake;
                ComPort.ReadTimeout = 5000;
                ComPort.Open();
            }
        }

        // Compute the LRC of the sending string
        // LRC = result of XOR between ACK, length, and message characters
        byte ComputeLRC(byte[] bArr, int bytecount)
        {
            byte TRC = 0;
            int count = 0;
            while (count++ < bytecount)
            {
                TRC = (byte)(TRC ^ bArr[count - 1]);
            }
            return TRC;
        }

        // Check if the packet received is complete
        bool IsComplete(byte[] byteArray, int length)
        {
            if (length > 3)
            {
                byte len = byteArray[2];
                if (length == len + 4)
                {
                    return true;
                }
            }
            return false;
        }

        // Send an array of bytes to the serial port
        void SendReceive(string Message, ref string Response, int InputTimeOutInSec)
        {
            int RetryNACK = NumberOfRetries;
        SendLabel:   // Used when receiving a NACK response, comand sending must be resent
            /*-----------------------------------*/
            /*     Send data to the Serial Port  */
            /*-----------------------------------*/

            #region Send data to the Serial Port
            //Convert Hex String to Byte Array
            byte[] myBuffer = new byte[Message.Length / 2]; // reserve 2 byte for CRC
            myBuffer = HexStringDecoder.StringToByteArray(Message);
            if ((Message != ACK) && (Message != NACK))
            {
                    // TRC Calculation
                    byte TRC = ComputeLRC(myBuffer, myBuffer.Length - 1);
                    myBuffer[myBuffer.Length - 1] = TRC;
            }
            // Update Message to include Error detection bits
            Message = HexStringDecoder.ByteArrayToString(myBuffer, myBuffer.Length);
            ComPort.DiscardInBuffer();
            ComPort.DiscardOutBuffer();
            // Send single byte in the byte array so it can be parsed
            for (int i = 0; i < myBuffer.Length; i++)
            {
                ComPort.Write(myBuffer, i, 1);
            }
            #endregion

            /*-----------------------------------*/
            /* Receive data from the Serial Port */
            /*-----------------------------------*/

            #region Receive data from the Serial Port
            // Initialize receive related variables
            int TimeOutCycleCountDown = (InputTimeOutInSec * 1000 / TimeOutUnitInMiliSec);
            bool TimeOut = false;
            int bytecount = 0;
            byte[] byteArrayBuffer = new byte[MaxBytes];
            byte[] byteArrayReturn = new byte[MaxBytes];
            // Reset the data string received and the byte received
            Response = "";
            // Unnecessary to collect data if it's an ACK or NACK
            if ((Message == ACK) || (Message == NACK))
                return;
            // Do the loop if there's no timeout or the timeout cycle still exists
            while ((!TimeOut) && (TimeOutCycleCountDown > 0) && (bytecount < MaxBytes))
            {
                // Attempt to read from COM port
                if (1 == ComPort.Read(byteArrayBuffer, bytecount, 1))
                {
                    bytecount++;
                }
                else
                {
                    // If restart command was sent, then timeout is expected
                    if (Message.Length >= 4 + RestartReader.Length - 2
                        && Message.Substring(4, RestartReader.Length - 2) == RestartReader.Substring(0, RestartReader.Length - 2))
                    {
                        break;
                    }
                    // Else set error and return
                    error = ReaderError.NO_RESPONSE;
                    return;
                }
                // Check if end of packet has been received
                if (IsComplete(byteArrayBuffer, bytecount))
                {
                    TimeOut = true;
                }
            }
            // Copy data to Response
            byteArrayReturn = byteArrayBuffer;
            Response = HexStringDecoder.ByteArrayToString(byteArrayReturn, bytecount);
            #endregion

            /*-----------------------------------*
             *        Handling NACK reponse      *
             *-----------------------------------*/

            #region Handling NACK reponse
            // Prepare to resend the command
            if ((Response != null) && (Response.Length >= NACK.Length) &&
                (Response.Substring(0, NACK.Length) == NACK) && (RetryNACK-- > 0))
            {
                // Terminate all the running command after a number of retries when receiving NACK response
                if (RetryNACK == 0)
                {
                    goto SendLabel;
                }
            }
            // Resend command because GemCore board has returned incorrect command form error
            // used when PCB is incorrect or for connect command
            else if ((Response != null) && (Response.Length >= 3)
                && (Response.Substring(0, 3).CompareTo("248") >= 0)
                  && !Message.Substring(Message.Length - 2, 2).Equals("C0"))
            {
                Message = NAD + getPCB() + Message.Substring(4);
                NumberOfGemCoreRetries--;
                if (NumberOfGemCoreRetries > 0)
                {
                    SendReceive(Message, ref Response, InputTimeOutInSec);
                }
            }
            else
            {
                NumberOfGemCoreRetries = 10;
            }
            #endregion
        }

        #endregion

        /*****************************************************************************/
        /*                                                                           */
        /*          Smart Card and Reader Communication                              */
        /*                                                                           */
        /*****************************************************************************/

        #region Smart Card

        // Retrieves the correct PCB value
        string getPCB()
        {
            // Last one was "00", so now send "40"
            if (pcb)
            {
                PCB = onPCB;
            }
            // Last one was "40", so now send "00"
            else
            {
                PCB = offPCB;
            }
            pcb = !pcb;
            return PCB;
        }

        // Validate responses
        void ResponseValidation(PacketParser packet)
        {
            // Infinite Loop detection 
            if ((packet.ErrorCode == PacketParser.ErrorType.Timeout)
                || (packet.ErrorCode == PacketParser.ErrorType.NoResponse)
                || (packet.ErrorCode == PacketParser.ErrorType.NACK))
            {
                error = ReaderError.NO_RESPONSE;
            }
            // Illegal response message detection
            else if (packet.ErrorCode == PacketParser.ErrorType.IllegalMessage)
            {
                error = ReaderError.BAD_MESSAGE;
            }
            // Illegal sent command detection
            else if (packet.ErrorCode == PacketParser.ErrorType.UnknownCommand
                || packet.ErrorCode == PacketParser.ErrorType.NumberArgs
                || packet.ErrorCode == PacketParser.ErrorType.BadCommandCode
                || packet.ErrorCode == PacketParser.ErrorType.NumberParams
                || packet.ErrorCode == PacketParser.ErrorType.CommandNotSupported)
            {
                error = ReaderError.BAD_COMMAND;
            }
            // Card malfunction detection
            else if (packet.ErrorCode == PacketParser.ErrorType.CardMissing
                || packet.ErrorCode == PacketParser.ErrorType.CardRemoved
                || packet.ErrorCode == PacketParser.ErrorType.CardMalfunction
                || packet.ErrorCode == PacketParser.ErrorType.PoweredDown
                || packet.ErrorCode == PacketParser.ErrorType.ShortCircuit)
            {
                error = ReaderError.NO_CARD_INSERTED;
            }
            // Other error detection
            else if (packet.ErrorCode != PacketParser.ErrorType.None)
            {
                error = ReaderError.UNKNOWN_ERROR;
            }
            // Correct data response
            else
            {
                error = ReaderError.NONE;
            }
        }

        // Connect reader
        internal override bool ConnectReader()
        {
            error = ReaderError.NONE;
            string Response = null;
            // Set port properties
            baudrate = 9600;
            // If reader is already on, no need to turn on again
            if (IsSerialPortOn)
            {
                error = ReaderError.ALREADY_CONNECTED;
                return true;
            }
            NumberOfGemCoreRetries = 10;
            try
            {
                // Open COM port
                InitializeComPort(baudrate, System.IO.Ports.Handshake.None);
                // Collect number of Retries if NACK receive
                NumberOfRetries = 5;
                // Send initialization command
                Config_Init = NAD + getPCB() + PowerOnSC;
                SendReceive(Config_Init, ref Response, InitializationTimeOut);
                Config_Init = NAD + getPCB() + PowerOffSC;
                SendReceive(Config_Init, ref Response, InitializationTimeOut);
                IsSerialPortOn = true;
                return true;
            }
            catch
            {
                if (ComPort.IsOpen)
                {
                    ComPort.Close();
                }
                error = ReaderError.PORT_CONNECTION_FAILED;
                return false;
            }
        }

        // Disconnect reader
        internal override bool DisconnectReader()
        {
            error = ReaderError.NONE;
            // Cannot disconnect reader if sending APDU
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Reader is already off
            else if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return true;
            }
            // Close Port, reset port and board flags
            ComPort.Close();
            IsSerialPortOn = false;
            return true;
        }

        // Reset reader
        internal override bool ResetReader()
        {
            error = ReaderError.NONE;
            // Send Restart Command and connect reader
            string Response = null;
            Config_Init = NAD + getPCB() + RestartReader;
            SendReceive(Config_Init, ref Response, InitializationTimeOut);
            IsSerialPortOn = false;
            return ConnectReader();
        }

        // Update status
        void UpdateStatus()
        {
            error = ReaderError.NONE;
            // Cannot update status of reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return;
            }
            // Cannot update status if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return;
            }
            // Cannot update status if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return;
            }
            // Create status command string
            string StatusCmdStr = NAD + getPCB() + CheckStatus;
            try
            {
                // Reset activated flag in case card is no longer activated
                CardActivated = false;
                // Send command to reader
                string Response = null;
                SendReceive(StatusCmdStr, ref Response, InitializationTimeOut);
                // Parse Response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If error, card is in error mode
                if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    error = ReaderError.UNKNOWN_ERROR;
                }
                else
                {
                    // Decode card's response
                    switch (int.Parse(Response.Substring(9, 1)))
                    {
                        case 0:
                            atr = null;
                            SlotStatus = NOCARD;
                            break;
                        case 4:
                            atr = null;
                            SlotStatus = PRESENT;
                            break;
                        case 6:
                            SlotStatus = ACTIVATED;
                            CardActivated = true;
                            break;
                        default:
                            atr = null;
                            SlotStatus = ERROR;
                            return;
                    }
                }
                return;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return;
            }
        }

        // Send User SlotStatus if able to update
        internal override int GetStatus()
        {
            UpdateStatus();
            return SlotStatus;
        }

        // Activate card
        internal override bool ActivateCard()
        {
            error = ReaderError.NONE;
            // Cannot activate card if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // If resetting card, the following will have already been checked
            if (!ResettingCard)
            {
                // Cannot activate card if sending APDU
                if ((!APDUCommandComplete))
                {
                    error = ReaderError.TEST_RUNNING;
                    return false;
                }
                // Cannot activate if already (de)activating
                if ((ActivatingSmartCard))
                {
                    error = ReaderError.ACTIVATING_CARD;
                    return false;
                }
                UpdateStatus();
                // No need to activate already activated card
                if (SlotStatus == ACTIVATED)
                {
                    error = ReaderError.NONE;
                    return true;
                }
                // Cannot activate card if there is no card
                else if (SlotStatus != PRESENT)
                {
                    error = ReaderError.NO_CARD_INSERTED;
                    return false;
                }
            }
            // Set activation command string
            string Response = null;
            string ActivationCmdStr = NAD + getPCB() + PowerOnSC;
            try
            {
                // Set activation flag
                ActivatingSmartCard = true;
                // Send command to reader
                SendReceive(ActivationCmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If no response, deactivate
                if (Packet.ErrorCode == PacketParser.ErrorType.NoResponse
                    || Packet.ErrorCode == PacketParser.ErrorType.NACK)
                {
                    string DeactivationCmdStr = NAD + getPCB() + PowerOffSC;

                    SendReceive(DeactivationCmdStr, ref Response, InitializationTimeOut);
                    Packet.GetPacket(Response);
                    ResponseValidation(Packet);

                    SlotStatus = ERROR;
                    error = ReaderError.NO_RESPONSE;
                    ActivatingSmartCard = false;
                    return false;
                }
                // If card malfunction, set error type appropriately
                else if (Packet.ErrorCode == PacketParser.ErrorType.CardMalfunction)
                {
                    SlotStatus = ERROR;
                    error = ReaderError.CARD_MALFUNCTION;
                    ActivatingSmartCard = false;
                    return false;
                }
                // Any other error, set status as error
                else if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    error = ReaderError.UNKNOWN_ERROR;
                    ActivatingSmartCard = false;
                    return false;
                }
                // No error means card is activated
                else
                {
                    SlotStatus = ACTIVATED;
                    atr = Response.Substring(8, Response.Length - 2 - 8);
                }
                // Update activation flags
                ActivatingSmartCard = false;
                CardActivated = true;
                return true;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Send PPS
        bool SendPPS()
        {
            error = ReaderError.NONE;
            // Cannot send PPS if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // Cannot send PPS if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Cannot send PPS if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return false;
            }
            UpdateStatus();
            // Cannot send PPS if card is deactivated
            if (SlotStatus != ACTIVATED)
            {
                error = ReaderError.CARD_DEACTIVATED;
                return false;
            }
            // Error checking for protocol
            else if ((!Protocol.Equals("00")) && (!Protocol.Equals("01")))
            {
                error = ReaderError.BAD_PROTOCOL;
                return false;
            }
            // Set command string for PPS
            string SetParametersCmdStr = NAD + getPCB() + SetParametersCommand + PRT +
                                          FiDi + TC1 + TC2_TA3 + RFU_TB3 + "FF";
            try
            {
                // Send PPS to card
                string Response = null;
                SendReceive(SetParametersCmdStr, ref Response, InitializationTimeOut);
                // Parse Response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                return true;
            }
            catch
            {
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Send PPS with user defined parameters
        internal override bool SendPPS(byte protocol)
        {
            error = ReaderError.NONE;
            // Set data from protocol
            byte prt = (byte)HexStringDecoder.HexStringToInt(PRT);
            Protocol = HexStringDecoder.ByteToHex(protocol);
            if (protocol == 1)
            {
                prt |= 8;           // Set protocol bit to 1
                TC2_TA3 = "20";   // Set TA3 = IFSC to 32
                RFU_TB3 = "00";   // Set TB3 = BWI/CWI to 0/0
            }
            else if (protocol == 0)
            {
                prt &= 0xF7;        // Set protocol bit to 0
                TC2_TA3 = "10";   // Set TC2 = WI to 16
                RFU_TB3 = "00";   // Set RFU to 0
            }
            else
            {
                error = ReaderError.BAD_PROTOCOL;
                return false;
            }
            // Set PRT based on current PCB value
            if (pcb)
            {
                prt &= 0xFD;        // Set next PCB to "00"
            }
            else
            {
                prt |= 2;           // Set next PCB to "40"
            }
            PRT = HexStringDecoder.ByteToHex(prt);
            // Send PPS with updated PRT
            return SendPPS();
        }

        // Deactivate card
        internal override bool DeactivateCard()
        {
            error = ReaderError.NONE;
            // Cannot deactivate card if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // If resetting card, the following has already been done
            if (!ResettingCard)
            {
                // Cannot deactivate if currently activating
                if (ActivatingSmartCard)
                {
                    error = ReaderError.ACTIVATING_CARD;
                    return false;
                }
                // Cannot deactivate if APDU is being sent
                else if ((!APDUCommandComplete))
                {
                    error = ReaderError.TEST_RUNNING;
                    return false;
                }
                UpdateStatus();
                // No need to deactivate if card is already deactivated
                if (SlotStatus == PRESENT)
                {
                    error = ReaderError.CARD_DEACTIVATED;
                    return true;
                }
                // Cannot deactivate if there is no card
                else if (SlotStatus != ACTIVATED)
                {
                    error = ReaderError.NO_CARD_INSERTED;
                    return false;
                }
            }
            // Create deactivation command string
            string DeactivationCmdStr = NAD + getPCB() + PowerOffSC;
            try
            {
                // Set deactivating flag
                ActivatingSmartCard = true;
                // Send command to reader
                string Response = null;
                SendReceive(DeactivationCmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                // If error, assume card was not deactivated
                if (Packet.ErrorCode != PacketParser.ErrorType.None)
                {
                    SlotStatus = ERROR;
                    atr = null;
                    error = ReaderError.UNKNOWN_ERROR;
                    CardActivated = false;
                    ActivatingSmartCard = false;
                    return false;
                }
                else
                {
                    SlotStatus = PRESENT;
                    atr = null;
                }
                // Reset flags
                ActivatingSmartCard = false;
                CardActivated = false;
                return true;
            }
            catch
            {
                ActivatingSmartCard = false;
                CardActivated = false;
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return false;
            }
        }

        // Reset card
        internal override bool ResetCard()
        {
            error = ReaderError.NONE;
            // Reader must be on to reset card
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return false;
            }
            // Cannot reset card while sending APDU
            else if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return false;
            }
            // Cannot reset card if activiating card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return true;
            }
            UpdateStatus();
            // Cannot reset card if not there
            if (SlotStatus == NOCARD)
            {
                error = ReaderError.NO_CARD_INSERTED;
                return false;
            }
            // Set reset flag
            ResettingCard = true;
            // Deactivate card if necessary, then reactivate
            if (CardActivated)
            {
                DeactivateCard();
            }
            bool ret = ActivateCard();
            ResettingCard = false;
            return ret;
        }

        // Checks to see if reader is Connected or not
        internal override bool IsReaderActive()
        {
            error = ReaderError.NONE;
            return IsSerialPortOn;
        }

        // Executes a Command given as a parameter
        internal override string ExecuteCommand(string commandCode, string data)
        {
            error = ReaderError.NONE;
            // Cannot send command if reader is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return null;
            }
            // Cannot send command if APDU is being sent
            if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return null;
            }
            // Cannot send command if activiating card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return null;
            }
            // Create command string
            string CmdStr = NAD + getPCB()
                    + HexStringDecoder.ByteToHex((byte)((commandCode.Length / 2) + (data.Length / 2)))
                    + commandCode + data + "FF";
            try
            {
                // Send command to reader
                string Response = null;
                SendReceive(CmdStr, ref Response, InitializationTimeOut);
                // Parse response
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                return Response;
            }
            catch
            {
                error = ReaderError.UNKNOWN_ERROR;
                return null;
            }
        }

        // Get current error
        internal override ReaderError Error
        {
            get
            {
                return error;
            }
        }

        // Get ATR
        internal override string ATR
        {
            get
            {
                return atr;
            }
        }

        // Send APDU to smart card
        internal override string SendAPDU(string apduText)
        {
            error = ReaderError.NONE;
            // Cannot send APDU if serial port is off
            if (!IsSerialPortOn)
            {
                error = ReaderError.READER_OFF;
                return null;
            }
            // Cannot send APDU if another APDU is being sent
            else if ((!APDUCommandComplete))
            {
                error = ReaderError.TEST_RUNNING;
                return null;
            }
            // Cannot send APDU if activating smart card
            else if (ActivatingSmartCard)
            {
                error = ReaderError.ACTIVATING_CARD;
                return null;
            }
            UpdateStatus();
            // Cannot send APDU if card is deactivated
            if (SlotStatus != ACTIVATED)
            {
                error = ReaderError.CARD_DEACTIVATED;
                return null;
            }
            // Start Sending Procedure
            APDUCommandComplete = false;
            string Response = null;
            string APDUCmdStr = NAD + getPCB() +
                    HexStringDecoder.ByteToHex((byte)(apduText.Length / 2 + 1)) // Length
                    + "15" + apduText + "FF";
            // Send APDU string
            try
            {
                SendReceive(APDUCmdStr, ref Response, TestTimeOut);
                // Parse Response string
                Packet.GetPacket(Response);
                ResponseValidation(Packet);
                Response = Packet.resp;
                APDUCommandComplete = true;
                return Response;
            }
            catch
            {
                SlotStatus = ERROR;
                error = ReaderError.UNKNOWN_ERROR;
                return null;
            }

        }
        #endregion


    }
}
